﻿/*******************************************************************************

    Units of Measure for C# applications

    Copyright (C) 2013 Marek Aniola

    This program is provided to you under the terms of the license
    as published at http://unitsofmeasure.codeplex.com/license


********************************************************************************/
using System;
using System.Globalization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Man.UnitsOfMeasure;

namespace UnitsOfMeasure.Test
{
	[TestClass]
	public class QuantityTests
	{
		private static SIUnitsDouble dsi = new SIUnitsDouble();
		private static SIUnitsFloat fsi = new SIUnitsFloat();
		private static SIUnitsDecimal msi = new SIUnitsDecimal();

		[TestClass]
		public class Conversions
		{
			[TestMethod]
			[ExpectedException(typeof(System.InvalidOperationException))]
			public void InconvertibleThrowException()
			{
				Quantity<double> meters = dsi.Meter[5.0D];	// 5.0 meters
				Quantity<double> metersInKilograms = dsi.Kilogram[meters];
			}

			[TestMethod]
			public void TemperatureSamples()
			{
				{
					Quantity<double> celsius = dsi.Celsius[100.0];
					Quantity<double> fahrenheit = dsi.Fahrenheit[celsius];
					Quantity<double> rankine = dsi.Rankine[fahrenheit];
					Quantity<double> kelvin = dsi.Kelvin[rankine];
					celsius = dsi.Celsius[kelvin];

					Assert.IsTrue(180.0 == fahrenheit.Value, "Celsius-to-Fahrenheit conversion (double) failed");
					Assert.IsTrue(180.0 == rankine.Value, "Fahrenheit-to-Rankine conversion (double) failed");
					Assert.IsTrue(100.0 == kelvin.Value, "Rankine-to-Kelvin conversion (double) failed");
					Assert.IsTrue(100.0 == celsius.Value, "Kelvin-to-Celsius conversion (double) failed");
				}
				{
					Quantity<float> celsius = fsi.Celsius[100.0F];
					Quantity<float> fahrenheit = fsi.Fahrenheit[celsius];
					Quantity<float> rankine = fsi.Rankine[fahrenheit];
					Quantity<float> kelvin = fsi.Kelvin[rankine];
					celsius = fsi.Celsius[kelvin];

					Assert.IsTrue(180.0F == fahrenheit.Value, "Celsius-to-Fahrenheit conversion (float) failed");
					Assert.IsTrue(180.0F == rankine.Value, "Fahrenheit-to-Rankine conversion (float) failed");
					Assert.IsTrue(100.0F == kelvin.Value, "Rankine-to-Kelvin conversion (float) failed");
					Assert.IsTrue(100.0F == celsius.Value, "Kelvin-to-Celsius conversion (float) failed");
				}
				{
					Quantity<decimal> celsius = msi.Celsius[100.0M];
					Quantity<decimal> fahrenheit = msi.Fahrenheit[celsius];
					Quantity<decimal> rankine = msi.Rankine[fahrenheit];
					Quantity<decimal> kelvin = msi.Kelvin[rankine];
					celsius = msi.Celsius[kelvin];

					Assert.IsTrue(180.0M == fahrenheit.Value, "Celsius-to-Fahrenheit conversion (decimal) failed");
					Assert.IsTrue(180.0M == rankine.Value, "Fahrenheit-to-Rankine conversion (decimal) failed");
					Assert.IsTrue(100.00000000000000000000000001M == kelvin.Value, "Rankine-to-Kelvin conversion (decimal) failed");
					Assert.IsTrue(100.00000000000000000000000001M == celsius.Value, "Kelvin-to-Celsius conversion (decimal) failed");
				}
			}

			[TestMethod]
			public void LengthSamples()
			{
				{
					Quantity<double> meters = dsi.Meter[100.0];				// == 100.0 m
					Quantity<double> centimeters = meters[dsi.Centimeter];	// == 10000.0 cm
					Quantity<double> inches = centimeters[dsi.Inch];		// == 3937.0078740157483 in
					Quantity<double> feet = inches[dsi.Foot];				// == 328.08398950131232 ft
					Quantity<double> yards = feet[dsi.Yard];				// == 109.36132983377077 yd
					meters = yards[dsi.Meter];								// == 99.999999999999986 m

					Assert.IsTrue(10000.0 == centimeters.Value, "Meter-to-Centimeter conversion (double) failed");
					Assert.IsTrue(3937.0078740157483 == inches.Value, "Centimeter-to-Inch conversion (double) failed");
					Assert.IsTrue(328.08398950131232 == feet.Value, "Inch-to-Feet conversion (double) failed");
					Assert.IsTrue(109.36132983377077 == yards.Value, "Feet-to-Yard conversion (double) failed");
					Assert.IsTrue(99.999999999999986 == meters.Value, "Yard-to-Meter conversion (double) failed");
				}
				{
					Quantity<float> meters = fsi.Meter[100.0F];				// == 100.0 m
					Quantity<float> centimeters = meters[fsi.Centimeter];	// == 10000.0 cm
					Quantity<float> inches = centimeters[fsi.Inch];			// == 3937.00781 in
					Quantity<float> feet = inches[fsi.Foot];				// == 328.083984 ft 
					Quantity<float> yards = feet[fsi.Yard];					// == 109.36132 yd
					meters = yards[fsi.Meter];								// == 99.99999 m

					Assert.IsTrue(10000.0F == centimeters.Value, "Meter-to-Centimeter conversion (float) failed");
					Assert.IsTrue(3937.00781F == inches.Value, "Centimeter-to-Inch conversion (float) failed");
					Assert.IsTrue(328.083984F == feet.Value, "Inch-to-Feet conversion (float) failed");
					Assert.IsTrue(109.36132F == yards.Value, "Feet-to-Yard conversion (float) failed");
					Assert.IsTrue(99.99999F == meters.Value, "Yard-to-Meter conversion (float) failed");
				}
				{
					Quantity<decimal> meters = msi.Meter[100.0M];			// == 100 m
					Quantity<decimal> centimeters = meters[msi.Centimeter];	// == 10000 cm
					Quantity<decimal> inches = centimeters[msi.Inch];		// == 3937.0078740157480314960629921 in
					Quantity<decimal> feet = inches[msi.Foot];				// == 328.08398950131233595800524934 ft
					Quantity<decimal> yards = feet[msi.Yard];				// == 109.36132983377077865266841645 yd
					meters = yards[msi.Meter];

					Assert.IsTrue(10000.0M == centimeters.Value, "Meter-to-Centimeter conversion (decimal) failed");
					Assert.IsTrue(3937.007874015748031496062992M == inches.Value, "Centimeter-to-Inch conversion (decimal) failed");
					Assert.IsTrue(328.0839895013123359580052492M == feet.Value, "Inch-to-Feet conversion (decimal) failed");
					Assert.IsTrue(109.36132983377077865266841639M == yards.Value, "Feet-to-Yard conversion (decimal) failed");
					Assert.IsTrue(99.99999999999999999999999995M == meters.Value, "Yard-to-Meter conversion (decimal) failed");
				}
			}
		}

		[TestClass]
		public class ComaprisonOperators
		{
			[TestMethod]
			public void FloatingPointParanoia()
			{
				Assert.IsTrue(dsi.KilometerPerHour[123.45] == dsi.KilometerPerHour[123.45]);
				Assert.IsTrue(msi.KilometerPerHour[123.45M] == msi.KilometerPerHour[123.45M]);
				Assert.IsTrue(fsi.KilometerPerHour[123.45F] == fsi.KilometerPerHour[123.45F]);
			}

			[TestMethod]
			[ExpectedException(typeof(System.InvalidOperationException))]
			public void InconvertibleThrowException()
			{
				Quantity<decimal> meters = msi.Meter[5.0M];	// 5 meters
				Quantity<decimal> kilograms = msi.Kilogram[5.0M];	// 5 kilograms
				bool test = (meters == kilograms);
			}

			[TestMethod]
			public void CompareConvertedValues()
			{
				{
					// 5.0 tonnes == 5000 kilograms
					Quantity<double> tonnes = dsi.Tonne[5.0];
					Quantity<double> kilograms = dsi.Kilogram[5000.0];
					Assert.IsTrue((tonnes.Value != kilograms.Value) && (tonnes == kilograms));
				}
				{
					// 5 pounds < 5 kilograms
					Quantity<float> pounds = fsi.Pound[5.0F];
					Quantity<float> kilograms = fsi.Kilogram[5.0F];
					Assert.IsTrue((kilograms.Value == pounds.Value) && (pounds != kilograms) && (pounds < kilograms) && (kilograms >= pounds));
				}
				{
					// 5 tonnes > 3000 kilograms
					Quantity<decimal> tonnes = msi.Tonne[5.0M];
					Quantity<decimal> kilograms = msi.Kilogram[3000.0M];
					Assert.IsTrue((tonnes.Value < kilograms.Value) && (tonnes != kilograms) && (tonnes > kilograms) && (kilograms <= tonnes));
				}
			}
		}


		[TestClass]
		public class AdditiveOperators
		{
			[TestMethod]
			[ExpectedException(typeof(System.InvalidOperationException))]
			public void InconvertibleThrowException()
			{
				Quantity<double> meters = dsi.Meter[5.0];	// 5 meters
				Quantity<double> kilograms = dsi.Kilogram[5.0];	// 5 kilograms
				Quantity<double> sum = meters + kilograms;
			}

			[TestMethod]
			public void ResultTakesUnitFromFirstArgument()
			{
				{
					Quantity<double> meters = dsi.Meter[5.0];	// 5 meters
					Quantity<double> centimeters = dsi.Centimeter[25.0];	// 25 centimeters

					Quantity<double> result = meters + centimeters;
					Assert.IsTrue((result.Unit == dsi.Meter) && (result.Value == 5.25));

					result = centimeters + meters;
					Assert.IsTrue((result.Unit == dsi.Centimeter) && (result.Value == 525.0));

					result = meters - centimeters;
					Assert.IsTrue((result.Unit == dsi.Meter) && (result.Value == 4.75));

					result = centimeters - meters;
					Assert.IsTrue((result.Unit == dsi.Centimeter) && (result.Value == -475.0));
				}
			}

			[TestMethod]
			public void Commutativity()
			{
				{
					Quantity<double> x = dsi.Meter[5.0];	// 5 meters
					Quantity<double> y = dsi.Inch[25.0];	// 25 inches
					Assert.IsTrue((x + y) == (y + x));		// "+" is commutative
					Assert.IsTrue((x - y) == -(y - x));		// "-" is anti-commutative
				}
			}

			[TestMethod]
			public void Associativity()
			{
				{
					Quantity<double> x = dsi.Meter[5.0];	// 5 meters
					Quantity<double> y = dsi.Inch[25.0];	// 25 inches
					Quantity<double> z = dsi.Yard[2.0];		// 2 yards
					Assert.IsTrue(((x + y) + z) == (x + (y + z)));	// "+" is associative
					Assert.IsFalse(((x - y) - z) == (x - (y - z)));	// "-" is not associative
				}
			}

			[TestMethod]
			public void IncrementDecrement()
			{
				{
					Quantity<double> meters = dsi.Meter[5.0];
					Quantity<double> post = meters++;
					Assert.IsTrue(meters == dsi.Meter[6.0]);
					Quantity<double> pre = --meters;
					Assert.IsTrue((post == pre) && (pre == meters) && (meters == dsi.Meter[5.0]));
				}
			}
		}

		[TestClass]
		public class MultiplicativeOperators
		{
			[TestMethod]
			public void ResultHasMeasureButNoUnit()
			{
				Quantity<double> speed = dsi.MilePerHour[100.0];	// 100 mph
				Quantity<double> duration = dsi.Minute[30.0];		// 30 minutes
				Quantity<double> distance;

				distance = speed * duration;
				Assert.IsTrue(distance.Value == 3000.0);
				Assert.IsTrue((distance.Unit == dsi.MilePerHour * dsi.Minute) && !(distance.Unit is UnitOfMeasure<double>));
				Assert.AreEqual("3000[0.03728227153424*(L)]", distance.ToString(CultureInfo.InvariantCulture));

				Quantity<double> miles = dsi.Mile[distance];
				Assert.IsTrue(miles.Value == 50.0);
				Assert.IsTrue((miles.Unit == dsi.Mile) && (miles.Unit is UnitOfMeasure<double>));
				Assert.AreEqual("50 mil", miles.ToString(CultureInfo.InvariantCulture));

				Quantity<double> kilometers = dsi.Kilometer[distance];
				Assert.IsTrue(kilometers.Value == 80.4672);
				Assert.IsTrue((kilometers.Unit == dsi.Kilometer) && (kilometers.Unit is UnitOfMeasure<double>));
				Assert.AreEqual("80.4672 km", kilometers.ToString(CultureInfo.InvariantCulture));

				speed = miles / duration;
				Assert.IsTrue(speed.Value == 1.6666666666666667);
				Assert.IsTrue((speed.Unit == dsi.Mile / dsi.Minute) && !(speed.Unit is UnitOfMeasure<double>));
				Assert.AreEqual("1.66666666666667[0.03728227153424*(LT-1)]", speed.ToString(CultureInfo.InvariantCulture));

				speed = dsi.MilePerHour[speed];
				Assert.IsTrue(speed.Value == 100.0);
				Assert.IsTrue((speed.Unit == dsi.MilePerHour) && (speed.Unit is UnitOfMeasure<double>));
				Assert.AreEqual("100 mph", speed.ToString(CultureInfo.InvariantCulture));

				speed = distance / duration;
				Assert.IsTrue(speed.Value == 100.0);
				Assert.IsTrue((speed.Unit == dsi.MilePerHour) && !(speed.Unit is UnitOfMeasure<double>));
				Assert.AreEqual("100[2.2369362920544*(LT-1)]", speed.ToString(CultureInfo.InvariantCulture));

				speed = dsi.KilometerPerHour[speed];
				Assert.IsTrue(speed.Value == 160.93439999999998);
				Assert.IsTrue((speed.Unit == dsi.KilometerPerHour) && (speed.Unit is UnitOfMeasure<double>));
				Assert.AreEqual("160.9344 km/h", speed.ToString(CultureInfo.InvariantCulture));
			}

			[TestMethod]
			public void MultiplyOrDivideByNumber()
			{
				{
					Quantity<double> meters = dsi.Meter[5.0];	// 5 meters

					meters *= 2.0;	// 10 meters
					Assert.IsTrue(meters == dsi.Meter[10.0]);

					meters /= 5.0;	// 2 meters
					Assert.IsTrue(meters == dsi.Meter[2.0]);
				}
			}

			[TestMethod]
			public void DivideByUnit()
			{
				Quantity<double> length = dsi.Meter[15.0];
				Assert.IsTrue(length / dsi.Foot == length[dsi.Foot].Value);
				Assert.IsTrue(dsi.Foot[length / dsi.Foot] == length);
			}

			[TestMethod]
			public void ProductIsCommutative()
			{
				{
					Quantity<double> width = dsi.Meter[5.0];
					Quantity<double> heigth = dsi.Meter[2.0];

					Assert.IsTrue((width * heigth) == (heigth * width));
					Assert.IsTrue((2.0 * width) == (width * 2.0));
				}
			}

			[TestMethod]
			public void ProductIsAssociative()
			{
				{
					Quantity<double> width = dsi.Centimeter[1.0];
					Quantity<double> heigth = dsi.Meter[2.0];
					Quantity<double> depth = dsi.Meter[3.0];
					Assert.IsTrue(((width * heigth) * depth) == (width * (heigth * depth)));
				}
			}

			[TestMethod]
			public void DivisionIsNotAssociative()
			{
				{
					Quantity<double> width = dsi.Centimeter[150.0];
					Quantity<double> heigth = dsi.Meter[2.0];
					Quantity<double> area = width * heigth;
					Assert.IsFalse(((area / width) / heigth).Unit.Sense == (area / (width / heigth)).Unit.Sense);
				}
			}
		}

		[TestClass]
		public class Parsing
		{
			[TestMethod]
			public void TryParse()
			{
				CultureInfo invariantCulture = CultureInfo.InvariantCulture;
				// The below creates the string "3 125 000,00 m/s" with PL-specific
				// NumberGroupSeparator that looks like space character but in fact it is
				// a non-breaking space that can't be easily entered from keyboard
				CultureInfo specificCulture = new CultureInfo("pl-PL");
				string localizedQuantityWithGroupSeparator = String.Format(specificCulture, "{0:N} m/s", 3125000.0);

				{
					Quantity<double> result;

					// OK with space separating number and unit
					Assert.IsTrue(dsi.TryParse("123.45 mi/h", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == dsi.MilePerHour[123.45]);

					// OK w/o separating space
					Assert.IsTrue(dsi.TryParse("123.45mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == dsi.MilePerHour[123.45]);

					// OK with uppercase unit, unless UnitSystem is case-sensitive
					Assert.IsTrue(dsi.TryParse("123.45 MPH", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == dsi.MilePerHour[123.45]);

					// Failure, unit not found
					Assert.IsFalse(dsi.TryParse("123.45 [mi/h]", CultureInfo.InvariantCulture, out result));

					// Failure, bad syntax (default parser looks for the unit to the right of the number)
					Assert.IsFalse(dsi.TryParse("mph 123.45", CultureInfo.InvariantCulture, out result));

					// OK with culture specific input
					Assert.IsTrue(dsi.TryParse(localizedQuantityWithGroupSeparator, specificCulture, out result));
					Assert.IsTrue(result == dsi.MeterPerSec[3125000.0]);

					// OK with NaN, Infinity
					Assert.IsTrue(dsi.TryParse("NaN mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == dsi.MilePerHour[double.NaN]);
					Assert.IsTrue(dsi.TryParse("Infinity mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == dsi.MilePerHour[double.PositiveInfinity]);
					Assert.IsTrue(dsi.TryParse("-Infinity mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == dsi.MilePerHour[double.NegativeInfinity]);
				}
				{
					Quantity<decimal> result;

					// OK with space separating number and unit
					Assert.IsTrue(msi.TryParse("123.45 mi/h", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == msi.MilePerHour[123.45M]);

					// OK w/o separating space
					Assert.IsTrue(msi.TryParse("123.45mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == msi.MilePerHour[123.45M]);

					// OK with uppercase unit, unless UnitSystem is case-sensitive
					Assert.IsTrue(msi.TryParse("123.45 MPH", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == msi.MilePerHour[123.45M]);

					// Failure, unit not found
					Assert.IsFalse(msi.TryParse("123.45 [mi/h]", CultureInfo.InvariantCulture, out result));

					// Failure, bad syntax (default parser looks for the unit to the right of the number)
					Assert.IsFalse(msi.TryParse("mph 123.45", CultureInfo.InvariantCulture, out result));

					// OK with culture specific input
					Assert.IsTrue(msi.TryParse(localizedQuantityWithGroupSeparator, specificCulture, out result));
					Assert.IsTrue(result == msi.MeterPerSec[3125000.0M]);

					// Decimal does not recognize NaN and Infinity:
					Assert.IsFalse(msi.TryParse("NaN mph", CultureInfo.InvariantCulture, out result));
					Assert.IsFalse(msi.TryParse("Infinity mph", CultureInfo.InvariantCulture, out result));
					Assert.IsFalse(msi.TryParse("-Infinity mph", CultureInfo.InvariantCulture, out result));
				}
				{
					Quantity<float> result;

					// OK with space separating number and unit
					Assert.IsTrue(fsi.TryParse("123.45 mi/h", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == fsi.MilePerHour[123.45F]);

					// OK w/o separating space
					Assert.IsTrue(fsi.TryParse("123.45mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == fsi.MilePerHour[123.45F]);

					// OK with uppercase unit, unless UnitSystem is case-sensitive
					Assert.IsTrue(fsi.TryParse("123.45 MPH", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == fsi.MilePerHour[123.45F]);

					// Failure, unit not found
					Assert.IsFalse(fsi.TryParse("123.45 [mi/h]", CultureInfo.InvariantCulture, out result));

					// Failure, bad syntax (default parser looks for the unit to the right of the number)
					Assert.IsFalse(fsi.TryParse("mph 123.45", CultureInfo.InvariantCulture, out result));

					// OK with culture specific input
					Assert.IsTrue(fsi.TryParse(localizedQuantityWithGroupSeparator, specificCulture, out result));
					Assert.IsTrue(result == fsi.MeterPerSec[3125000.0F]);

					// OK with NaN, Infinity
					Assert.IsTrue(fsi.TryParse("NaN mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == fsi.MilePerHour[float.NaN]);
					Assert.IsTrue(fsi.TryParse("Infinity mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == fsi.MilePerHour[float.PositiveInfinity]);
					Assert.IsTrue(fsi.TryParse("-Infinity mph", CultureInfo.InvariantCulture, out result));
					Assert.IsTrue(result == fsi.MilePerHour[float.NegativeInfinity]);
				}
			}
		}
	}
}
